#pragma once
/*
    日志落地模块：将格式化后的日志消息，输出到指定的位置
    可以同时输出到不同的位置

    位置分类：
        1. 标准输出stdout
        2. 写入指定的文件中（常用）
        3. 滚动文件（按时间、大小进行滚动切换）例如：只保存 3天以内的日志消息、 只保存总大小1G的日志

        4. 支持落地方向扩展： 写入指定服务器、数据库【用户可以编写新的落地模块】
*/

/*
    实现：（多态）
        1. 抽象一个落地模块基类
        2. 不同的落地方向模块继承并重写落地方法
        3. 使用工厂模式进行创建与表示的分离
            【在格式化模块同样用到了这个设计模式。 基类FormatItem 派生类..NameFormatItem(重写基类的format方法)
                                               由新的Formatter按照规则调用各个子类方法】
*/
#include <iostream>
#include <memory>
#include <fstream>
#include <cassert>
#include <sstream>

#include "1_util.hpp"

namespace senlog
{
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink() {}
        virtual ~LogSink() {} // 为何这里需要设置虚析构？
        virtual void Log(const char *data, size_t len) = 0;
    };

    /*
        标准输出、指定文件、滚动文件
    */
    class StdoutLogSink : public LogSink
    {
        virtual void Log(const char *data, size_t len)
        {
            // 写入标准输出
            std::cout.write(data, len); // 从data位置开始，向cout写入len长度的字符
        }
    };
    class FileLogSink : public LogSink
    {
    private:
        std::string _pathname;
        std::ofstream _ofs; // 管理被打开的文件句柄， 以防止后续浪费资源在“频繁打开文件操作”中
    public:
        FileLogSink(const std::string &pathname)
            : _pathname(pathname)
        {
            // util::File::exists(pathname);
            util::File::createDirectory(util::File::path(pathname));

            _ofs.open(_pathname, std::ios::binary | std::ios::app); // binary以二进制读写方式打开，app追加写
            assert(_ofs.is_open());                                 // 文件是否成功打开且尚未关闭
        }
        virtual void Log(const char *data, size_t len)
        {
            // 写入标准输出
            _ofs.write(data, len);
            assert(_ofs.good()); // 判断流的整体状态是否良好
        }
    };
    class RollLogSink : public LogSink
    {
    private:
        std::string _base_filename; // 根据这个基础文件名，当大小超过1G 时，创建基于这个文件名的扩展文件名(与时间相关)
                                    //   ./log/base-   ./log/base-202507141252.log
        size_t _max_filesize;       // 按照文件大小滚动
                                    // 2. 定义一个计数器记录时间，若超过一个计数值，那么就创建一个新的日志文件
        std::ofstream _ofs;
        size_t _cur_filesize; // 当前的文件大小，需要每次写文件与_max_filesize对比
        int _count;
    private:
        std::string createNewFileName() // 统一创建文件接口
        {
            // 获取系统时间 ，来构建新的文件名
            time_t t = util::Date::getTime(); // 获取当前的时间戳
            struct tm lt;                     // 一个时间结构
            localtime_r(&t, &lt);             // 将时间戳转化并放入结构体中, 也就是将年月日之类的信息解析出来
            std::stringstream file_new_name;  // include sstream
            file_new_name << _base_filename;
            file_new_name << "-";
            file_new_name << lt.tm_year + 1900;
            file_new_name << lt.tm_mon + 1;
            file_new_name << lt.tm_mday;
            file_new_name << lt.tm_hour;
            file_new_name << lt.tm_min;
            file_new_name << lt.tm_sec;
            file_new_name << "(" <<_count++ << ")";
            file_new_name << ".log";
            // basename-年月日时分秒.log
            return file_new_name.str();
        }

    public:
        RollLogSink(const std::string &_base_filename, size_t maxsize)
            : _base_filename(_base_filename), _max_filesize(maxsize), _cur_filesize(0), _count(0)
        {
            std::string pathname = createNewFileName();
            util::File::createDirectory(util::File::path(pathname));

            _ofs.open(pathname, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }
        virtual void Log(const char *data, size_t len)
        {
            // 写入标准输出
            if (_cur_filesize >= _max_filesize)
            {
                // 一定要先关闭原来的流
                _ofs.close();
                // 创建新文件
                std::string newpathname = createNewFileName();
                util::File::createDirectory(util::File::path(newpathname));
                // 打开新流
                _ofs.open(newpathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
                _cur_filesize = 0;
            }
            _ofs.write(data, len);
            assert(_ofs.good());
            _cur_filesize += len;
        }
    };

    // 工厂模式
    // 1. 首先需要创建出不同的模式子项，但一个个手动调用创建对象很麻烦且不利于扩展
    // 所以这里使用模板参数，根据传入的SinkType不同，自动匹配对应的子项构造函数
    // 2. 但不同的子项构造函数需要不同的参数
    // 这里使用可变参数模板来接受

    class SinkFactory
    {
        // 简单工厂模式
    public:
        template <typename SinkType, typename... Args> // 模板 参数包类型 //为什么不能使用模板类？
        static LogSink::ptr create(Args &&...args) // 传入不定参数包
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...); // 将参数完美转发至这里
        }
    };
};